Dx.declare('Dx.Module', {

   '@singleton': true,

   '@requires': [

      'Dx.net.Client'

   ],

   '@accessors': {

      'rootUrl': '/',

      'baseUrl': 'js',

      // will be treated as defaults
      'async': true,

      'transport': null

   },

   '@actions': {

      // loads script file
      'loadScript': [

         'validate',

         'process'

      ],

      'register': [

         'initialize',

         'validate',

         'process'

      ],

      'declare': [

         'initialize',

         'validate',

         'process',

         'finalize'

      ],

      'getClassUrl': [

         'validate',

         'process'

      ],

      'getUrlClass': [

         'validate',

         'process'

      ]

   },

   transportNs: 'Dx.loader.transport',

   transportBase: 'Dx.net.Client',

   modules: undefined,

   dependencies: undefined,

   classLoadList: undefined,

   transports: undefined,

   transportGenId: 0,

   nameGenId: 0,

   allDotsRe: /\./g,

   allSlashRe: /\/|\\/g,

   hasJsExtensionNameRe: /\.js$/i,

   constructor: function() {

      var GLOBAL = Dx.GLOBAL;

      var Me = this;

      var pendingClassCallback = this._callback_onDeclareClass;

      this.modules = {};

      this.dependencies = {};

      this.transports = {};

      this.classLoadList = {};

      // resolve root url

      // is browser! use location object
      if (GLOBAL.window === GLOBAL) {

         this.setRootUrl(GLOBAL.location.pathname);

      }

      // bind pending class callbck to my instance
      this._callback_onDeclareClass = function() {

         return pendingClassCallback.apply(Me, arguments);

      };

   },

   _callback_transportProcessor: function(transport, success) {

      var id = transport.id;

      var list = this.transports;

      var data = list[id];

      data.callback.call(data.scope, data.url, data, success);

      // destroy data
      Dx.clearObject(data);

      // destroy transport
      delete list[id];

      transport.destroy();

   },

   _callback_onDeclareClass: function(className, instance, Prototoype) {

      var notifyClasses = this.dependencies[className];

      var modules = this.modules;

      var l, dependency, classToNotify, module;

      //-- notify dependencies
      for(l = notifyClasses.length; l--;) {

         classToNotify = notifyClasses[l];

         module = modules[classToNotify];

         // try declaring
         if (!module.declared) {

            this.declare(classToNotify);

         }

      }

   },

   _callback_onLoadClassFile: function(url, data, success) {

      var className = this.getUrlClass(url);

      var loadList = this.classLoadList;

      // valid class load
      if (loadList.hasOwnProperty(className)) {

         loadList[className] = success;

      }

   },

   loadScript_validate: function(o, path, callback, scope) {

      var D = Dx;

      var transport = this.getTransport();

      if (!D.isString(path, true) ||

         !D.isFunction(callback)

      ) {

         o.stop = true;

         return;

      }

      o.transport = transport;

      o.stop = !D.subclassOf(this.transportBase, transport);

   },

   loadScript_process: function(o, path, callback, scope) {

      var async = this.getAsync();

      var transport = new o.transport();

      var transportId = 'transport' + (++this.transportGenId);

      transport.id = transportId;

      this.transports[transportId] = {

         type: 'loadScript',

         url: path,

         callback: callback,

         instance: transport,

         scope: scope

      };

      // load module
      transport.request({

         'url': path,

         'async': this.getAsync(),

         'callback': this._callback_transportProcessor,

         'scope': this

      });


   },

   setTransport_validate: function(o, transport) {

      var D = Dx;

      o.stop = !D.isString(transport, true) ||

         !D.Class.isDeclared(this.transportNs + '.' + transport);

   },

   getTransport_initialize: function(o, transport) {

      var D = Dx;

      var path;

      o.returnValue = void(0);

      // use default
      if (arguments.length < 2) {

         transport = this.transport;

      }

      // finalize type name
      if (D.isString(transport, true)) {

         path = this.transportNs + '.' + transport;

         o.params[1] = transport;

         o.params[2] = path;

      } else {

         o.stop = true;

      }

   },

   getTransport_format: function(o, transport, path) {

      o.returnValue = Dx.getClass(path);

   },

   getClassUrl_validate: function(o, className) {

      o.returnValue = false;

      o.stop = !Dx.isString(className, true);

   },

   getClassUrl_process: function(o, className) {

      var baseUrl = this.baseUrl;

      var url = (

            this.hasJsExtensionNameRe.test(className) ?

               className.slice(0, className.length - 3) : className

         ).replace(this.allDotsRe, '/');

      o.returnValue = (

         baseUrl ?

            baseUrl + '/' : ''

      ) + url + '.js';

   },

   getUrlClass_validate: function(o, url) {

      o.returnValue = false;

      o.stop = !Dx.isString(url, true);

   },

   getUrlClass_process: function(o, url) {

      var baseUrl = this.baseUrl;

      var bl = baseUrl.length;

      var qIndex;

      if (bl) {

         baseUrl += '/';

         bl++;

      }

      if (url.substring(0, bl) == baseUrl) {

         url = url.substring(bl, url.length);

      }

      // remove query string
      qIndex = url.lastIndexOf('?');

      if (qIndex != -1) {

         url = url.substring(0, qIndex);

      }

      o.returnValue = (

            this.hasJsExtensionNameRe.test(url) ?

               url.substring(0, url.length - 3) : url

         ).replace(this.allSlashRe, '.');

   },

   register_initialize: function(o, name, requires, definition) {

      var D = Dx;

      var className = this.getUrlClass(name);

      if (!D.isArray(requires)) {

         requires = [];

      }

      o.params[1] = className;

      o.params[2] = requires;

      o.params[3] = definition;



      // stop if unable to resolve className
      o.stop = !className;

      o.returnValue = !o.stop;

   },

   register_validate: function(o, className, requires, definition) {

      var D = Dx;

      // allow register if:
      //    - class is not yet registered in Dx.Class (already loaded)
      o.stop = !D.isString(className, true) || D.Class.discover(className);

   },

   register_process: function(o, className, requires, definition) {

      var D = Dx;

      var DClass = D.Class;

      var modules = this.modules;

      var dependencies = this.dependencies;

      var loadList = this.classLoadList;

      var module, undeclared, l, dependencyList, requireClassName, discovered;

      // set classname as loaded if not defined
      if (!loadList.hasOwnProperty(className)) {

         loadList[className] = true;

      }

      // create module
      module = {

         name: className,

         declared: false,

         requires: requires,

         undeclaredRequires: [],

         definition: definition,

         declaration: {}

      };

      this.modules[className] = module;

      undeclared = module.undeclaredRequires;

      // filter and process require list
      for(l = requires.length; l--;) {

         requireClassName = this.getUrlClass(requires[l]);

         // if not valid: skip
         if (!requireClassName) {

            undeclared.splice(l, 1);

            requires.splice(l, 1);

            continue;

         }

         // try discovering the class in ns path
         discovered = DClass.discover(requireClassName);

         requires[l] = requireClassName;

         // if declared: skip this class
         if (DClass.isDeclared(requireClassName)) {

            undeclared.splice(l, 1);

         // prepare required class declaration
         } else {

            undeclared[l] = requireClassName;

            // setup class load dependency
            if (!dependencies.hasOwnProperty(requireClassName)) {

               dependencies[requireClassName] = [className];

               // setup callback
               D.onDeclare(requireClassName, this._callback_onDeclareClass);

            } else {

               dependencyList = dependencies[requireClassName];

               dependencyList[dependencyList.length] = className;

            }


            // allow load class if:
            //    - class url is not yet in load list
            //    - class is not yet registered in Dx.Class (already loaded)
            if (!discovered && !loadList.hasOwnProperty(requireClassName)) {

               // set load list
               loadList[requireClassName] = null;

               this.loadScript(

                  this.getClassUrl(requireClassName),

                  this._callback_onLoadClassFile,

                  this

               );

            }

         }

      }

      // declare if no more required classes needed for this class to declare
      if (!undeclared.length) {

         this.declare(className);

      }


   },

   declare_initialize: function(o, className) {

      o.returnValue = false;

      o.stop = !Dx.isString(className, true);

   },

   declare_validate: function(o, className) {

      var D = Dx;

      var DClass = D.Class;

      var list = this.modules;

      var module = list.hasOwnProperty(className) ? list[className] : void(0);

      var undeclared, l;

      o.stop = true;

      // allow declare module if:
      //    - class is registered as module
      //    - all required classes were declared
      //    - class is not yet registered in Dx.Class (already loaded)
      if (module && !module.declared && !DClass.isRegistered(className)){

         // check for undeclared required classes
         undeclared = module.undeclaredRequires;

         for(l = undeclared.length; l--;) {

            // remove from undeclared
            if (DClass.isDeclared(undeclared[l])) {

               undeclared.splice(l, 1);

            }

         }

         // do not declare if there are still undeclared required classes
         o.stop = undeclared.length > 0;

      }

   },

   declare_process: function(o, className) {

      var D = Dx;

      var DClass = D.Class;

      var module = this.modules[className];

      var definition = module.definition;

      var requires = module.requires;

      var declaration = module.declaration;

      var args, l, requireName, exports;

      // create exports from definition function result
      if (D.isFunction(definition)) {

         // create arguments
         args = [];

         for(l = requires.length; l--;) {

            args[l] = D.getInstance(requires[l]);

         }

         exports = definition.apply(module, args);

      }

      // apply exports
      if (D.isObject(exports)) {

         D.assign(declaration, exports);

      }

      // finalize if this class to declare is public or not
      if (!('@public' in declaration) ||

         !D.isBooean(declaration['@public'])

      ) {

         declaration['@public'] = false;

      }

      o.params[2] = module;

   },

   declare_finalize: function(o, className, module) {

      module.declared = true;

      Dx.declare(className, module.declaration);

   }

});


/**
 * global configurations
 */
Dx.configurations = {};

/**
 * creates/updates global configurations
 */
Dx.configure = function(url, value) {
   
   var D = Dx;
   
   var configurations = D.configurations;
   
   // default namespace
   var ns = 'Dx';
   
   var name = url;
   
   var index;
   
   if (D.isString(url, true)) {
      
      index = url.indexOf(':');
      
      if (index != -1) {
         
         if (index > 1) {

            ns = url.substring(0, index);
         
         }
         
         name = url.substring(index + 1, url.length);
         
      }
      
      // apply configuration
      if (name.length) {
         
         if (!configurations.hasOwnProperty(ns)) {
            
            configurations[ns] = {};
            
         }
         
         configurations[ns][name] = value;
      
      }
      
   } else if (D.isObject(url)) {
      
      D.each(url, D._callback_configure, D);
      
   }
   
};

Dx._callback_configure = function(name, value, configurations) {
   
   if (name && configurations.hasOwnProperty(name)) {
      
      this.configure(name, value);
      
   }
   
};

Dx.define = function(name, requires, definition) {

   var D = Dx;

   var M = D.Module;

   var l = arguments.length;

   // finalize parameters
   definition = l ? arguments[--l] : void(0);

   requires = l ? arguments[--l] : void(0);

   if (D.isString(requires, true)) {

      name = requires;

      requires = [];

   } else {

      if (!D.isArray(requires)) {

         requires = [];

      }

      name = l ? arguments[--l] : void(0);

   }

   M.register(name, requires, definition);


};
